#pragma once

#include <deque>
#include <mutex>
#include <condition_variable>

template <typename T>
class UnBoundedQueue
{
public:
    ~UnBoundedQueue()
    {
        Quit();
    }

    void Quit()
    {
        {
            std::lock_guard<std::mutex> guard{mutex_};
            quit_ = true;
        }
        cond_notEmpty_.notify_all();
    }

    bool Empty() const
    {
        std::lock_guard<std::mutex> guard{mutex_};
        return deque_.empty();
    }

    void Clear()
    {
        std::lock_guard<std::mutex> guard{mutex_};
        deque_.clear();
    }

    std::size_t Size() const noexcept
    {
        std::lock_guard<std::mutex> guard{mutex_};
        return deque_.size();
    }

    bool Push(const T &val)
    {
        return Add(val);
    }

    bool Push(T &&val)
    {
        return Add(std::forward<T>(val));
    }

    bool Pop(T &val)
    {
        std::unique_lock lock{mutex_};
        cond_notEmpty_.wait(lock, [this]
                            {
                                return quit_ || !deque_.empty();
                            });
        if (quit_)
        {
            return false;
        }

        val = std::move(deque_.front());
        deque_.pop_front();

        return true;
    }

    bool tryPop(T &val)
    {
        std::lock_guard<std::mutex> guard{mutex_};
        if (quit_ || deque_.empty())
        {
            return false;
        }

        val = std::move(deque_.front());
        deque_.pop_front();

        return true;
    }

    bool Pop(std::deque<T> &deque)
    {
        std::unique_lock lock{mutex_};
        cond_notEmpty_.wait(lock, [this]
                            {
                                return quit_ || !deque_.empty();
                            });
        if (quit_)
        {
            return false;
        }

        deque_.swap(deque);

        return true;
    }

    bool tryPop(std::deque<T> &deque)
    {
        std::lock_guard<std::mutex> guard{mutex_};
        if (quit_ || deque_.empty())
        {
            return false;
        }

        deque_.swap(deque);

        return true;
    }

private:
    mutable std::mutex mutex_;
    std::deque<T> deque_;
    std::condition_variable cond_notEmpty_;
    bool quit_ = false;

    template <typename F>
    bool Add(F &&f)
    {
        std::lock_guard<std::mutex> guard{mutex_};
        if (quit_)
        {
            return false;
        }
        deque_.emplace_back(std::forward<F>(f));
        cond_notEmpty_.notify_one();

        return true;
    }
};

template <typename T, std::size_t capacity>
class BoundedQueue
{
    static_assert(capacity > 0, "BoundedQueue capacity must be greater than 0");

public:
    ~BoundedQueue()
    {
        Quit();
    }

    void Quit()
    {
        {
            std::lock_guard<std::mutex> guard{mutex_};
            quit_ = true;
        }
        cond_notFull_.notify_all();
        cond_notEmpty_.notify_all();
    }

    void Clear()
    {
        std::lock_guard<std::mutex> guard{mutex_};
        deque_.clear();
        cond_notFull_.notify_one();
    }

    bool Empty() const
    {
        auto lock = std::lock_guard{mutex_};
        return deque_.empty();
    }

    bool Full() const
    {
        std::lock_guard<std::mutex> guard{mutex_};
        return deque_.size() >= capacity;
    }

    std::size_t Size() const noexcept
    {
        std::lock_guard<std::mutex> guard{mutex_};
        return deque_.size();
    }

    bool Push(const T &val)
    {
        return Add(val);
    }

    bool tryPush(const T &val)
    {
        return tryPushImpl(val);
    }

    bool Push(T &&val)
    {
        return Add(std::forward<T>(val));
    }

    bool tryPush(T &&val)
    {
        return tryPushImpl(std::forward<T>(val));
    }

    bool Pop(T &val)
    {
        std::unique_lock lock{mutex_};
        cond_notEmpty_.wait(lock, [this]
                            {
                                return quit_ || !deque_.empty();
                            });
        if (quit_)
        {
            return false;
        }

        val = std::move(deque_.front());
        deque_.pop_front();
        cond_notFull_.notify_one();

        return true;
    }

    bool tryPop(T &val)
    {
        std::lock_guard<std::mutex> guard{mutex_};
        if (quit_ || deque_.empty())
        {
            return false;
        }

        val = std::move(deque_.front());
        deque_.pop_front();
        cond_notFull_.notify_one();

        return true;
    }

    bool Pop(std::deque<T> &deque)
    {
        std::unique_lock lock{mutex_};
        cond_notEmpty_.wait(lock, [this]
                            {
                                return quit_ || !deque_.empty();
                            });
        if (quit_)
        {
            return false;
        }

        deque_.swap(deque);
        cond_notFull_.notify_one();

        return true;
    }

    bool tryPop(std::deque<T> &deque)
    {
        std::lock_guard<std::mutex> guard{mutex_};
        if (quit_ || deque_.empty())
        {
            return false;
        }

        deque_.swap(deque);
        cond_notFull_.notify_one();

        return true;
    }

private:
    mutable std::mutex mutex_;
    std::deque<T> deque_;
    std::condition_variable cond_notFull_;
    std::condition_variable cond_notEmpty_;
    bool quit_ = false;

    template <typename F>
    bool Add(F &&f)
    {
        std::unique_lock lock{mutex_};
        cond_notFull_.wait(lock, [this]
                           {
                               return quit_ || deque_.size() < capacity;
                           });
        if (quit_)
        {
            return false;
        }

        deque_.emplace_back(std::forward<F>(f));
        cond_notEmpty_.notify_one();

        return true;
    }

    template <typename F>
    bool tryPushImpl(F &&f)
    {
        std::lock_guard<std::mutex> guard{mutex_};
        if (quit_ || deque_.size() >= capacity)
        {
            return false;
        }

        deque_.emplace_back(std::forward<F>(f));
        cond_notEmpty_.notify_one();

        return true;
    }
};
